#pragma once
#include <elasticlient/client.h>
#include <cpr/response.h>
#include <json/json.h>
#include <memory>
#include <iostream>
#include "Logger.hpp"

namespace XKL
{
    bool Serialize(const Json::Value &val, std::string &dst)
    {
        Json::StreamWriterBuilder swb;
        swb.settings_["emitUTF8"] = true;
        std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
        std::stringstream ss;
        if (sw->write(val, &ss) != 0)
        {
            return false;
        }
        dst = ss.str();
        return true;
    }

    bool UnSerialize(const std::string &src, Json::Value &val)
    {
        Json::CharReaderBuilder crb;
        std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
        std::string err;
        if (cr->parse(src.c_str(), src.c_str() + src.size(), &val, &err) == false)
            return false;
        return true;
    }
    // 核心内容主要是对正文内容的处理
    class ESIndex
    {
    public:
        ESIndex(std::shared_ptr<elasticlient::Client> &client, const std::string &index_name, const std::string &type = "_doc")
            : _index_name(index_name), _type(type), _client(client)
        {
            Json::Value tokenizer;
            tokenizer["tokenizer"] = "ik_max_word";
            Json::Value ik;
            ik["ik"] = tokenizer;
            Json::Value analyzer;
            analyzer["analyzer"] = ik;
            Json::Value analysis;
            analysis["analysis"] = analyzer;
            _index["settings"] = analysis;
            std::string tmp;
        }

        ESIndex &append(const std::string &key, const std::string &type = "text",
                        const std::string &analyer = "ik_max_word", bool enable = true)
        {
            Json::Value fields;
            fields["type"] = type;
            fields["analyer"] = analyer;
            if (enable == false)
                fields["enable"] = enable;
            _properties[key] = fields;

            return *this;
        }

        bool create(const std::string &id)
        {
            Json::Value mappings;
            mappings["dynamic"] = true;
            mappings["properties"] = _properties;
            _index["mappings"] = mappings;

            std::string body;
            if (Serialize(_index, body) == false)
            {
                LOG_ERROR("ESIndex serialize failed");
                return false;
            }
            try
            {
                auto rsp = _client->index(_index_name, _type, id, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("ESIndex create index {} failed, status_code : {} ", _index_name, rsp.status_code);
                    return false;
                }
            }
            catch (const std::exception &e)
            {
                LOG_ERROR("ESIndex create index {} failed, reason : {} ", _index_name, e.what());
                return false;
            }
            LOG_DEBUG("test index create sucessed!");
            return true;
        }

    private:
        std::string _index_name;
        std::string _type;
        Json::Value _properties;
        Json::Value _index;
        std::shared_ptr<elasticlient::Client> _client;
    };

    class ESInsert
    {
    public:
        ESInsert(std::shared_ptr<elasticlient::Client> &client, const std::string &index_name, const std::string &type = "_doc")
            : _index_name(index_name), _type(type), _client(client)
        {
        }

        ESInsert &append(const std::string &key, const std::string &val)
        {
            _item[key] = val;
            return *this;
        }

        bool insert(const std::string &id = "") // id ???
        {
            std::string body;
            if (!Serialize(_item, body))
            {
                LOG_WARN("ESInsert Serialize failed");
                return false;
            }
            try
            {
                auto rsp = _client->index(_index_name, _type, id, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("ESInsert insert data {} failed, status_code : {} ", _index_name, rsp.status_code);
                    return false;
                }
            }
            catch (const std::exception &e)
            {
                LOG_ERROR("ESInsert insert data {} failed, reason : {} ", _index_name, e.what());
                return false;
            }
            LOG_DEBUG("ESInsert successed");
            return true;
        }

    private:
        std::string _index_name;
        std::string _type;
        Json::Value _item;
        std::shared_ptr<elasticlient::Client> _client;
    };

    class ESSearch
    {
    public:
        ESSearch(std::shared_ptr<elasticlient::Client> &client, const std::string &index_name, const std::string &type = "_doc")
            : _index_name(index_name), _type(type), _client(client)
        {
        }

        ESSearch &appendMustNotTerms(const std::string &key, const std::vector<std::string> &vals)
        {
            Json::Value fields;
            for (const auto &val : vals)
                fields[key].append(val);
            Json::Value terms;
            terms["terms"] = fields;
            _must_not.append(terms);
            return *this;
        }

        ESSearch &appendShouldMatch(const std::string &key, const std::string &val)
        {
            Json::Value fields;
            fields[key] = val;
            Json::Value match;
            match["match"] = fields;
            _should.append(match);
            return *this;
        }

        Json::Value search()
        {
            Json::Value cond;
            if (!_must_not.empty())
                cond["must_not"] = _must_not;
            if (!_should.empty())
                cond["should"] = _should;
            Json::Value query;
            query["bool"] = cond;
            Json::Value root;
            root["query"] = query;

            std::string body;
            if (!Serialize(root, body))
            {
                LOG_WARN("ESSearch Serialize failed");
                return false;
            }
            cpr::Response rsp;
            try
            {
                rsp = _client->search(_index_name, _type, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("ESSearch search data {} failed, status_code : {} ", _index_name, rsp.status_code);
                    return Json::Value();
                }
            }
            catch (const std::exception &e)
            {
                LOG_ERROR("ESSearch search data {} failed, reason : {} ", _index_name, e.what());
                return Json::Value();
            }

            Json::Value json_res;
            if (!UnSerialize(rsp.text, json_res))
            {
                LOG_WARN("ESSearch UnSerialize failed");
                return false;
            }
            LOG_DEBUG("ESSearch successed");
            return json_res["hits"]["hits"];
        }

    private:
        std::string _index_name;
        std::string _type;
        Json::Value _must_not;
        Json::Value _should;
        std::shared_ptr<elasticlient::Client> _client;
    };

    class ESRemove
    {
    public:
        ESRemove(std::shared_ptr<elasticlient::Client> &client, const std::string &index_name, const std::string &type = "_doc")
            : _index_name(index_name), _type(type), _client(client)
        {
        }

        bool remove(const std::string &id)
        {
            try
            {
                auto rsp = _client->search(_index_name, _type, id);
                if (rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("ESRemove search data {} failed, status_code : {} ", _index_name, rsp.status_code);
                    return false;
                }
            }
            catch (const std::exception &e)
            {
                LOG_ERROR("ESRemove search data {} failed, reason : {} ", _index_name, e.what());
                return false;
            }

            LOG_DEBUG("ESRemove successed");
            return true;
        }

    private:
        std::string _index_name;
        std::string _type;
        std::shared_ptr<elasticlient::Client> _client;
    };
}